/**
 * Copyright (c) 2020 lizebang
 *
 * This software is released under the MIT License.
 * https://opensource.org/licenses/MIT
 */

package handler

import (
	"fmt"
	"sync"
	"time"

	"gitee.com/banggua/ggtsync/config"
	githttpbackend "gitee.com/banggua/ggtsync/handler/git-http-backend"
	"gitee.com/banggua/ggtsync/handler/jiri"
	"gitee.com/banggua/ggtsync/logger"
	"gitee.com/banggua/ggtsync/plugin"
)

type Handler interface {
	Test(*config.Project) error
	Templet() string
	Handle(*config.Project) error
	Close(*config.Project) error
}

type handlerManager struct {
	handlers  map[string]Handler
	locker    *sync.Mutex
	doneChs   map[string]chan struct{}
	closeOnce *sync.Once
	closeCh   chan struct{}
}

var (
	defaultHandlerManager = &handlerManager{
		handlers:  make(map[string]Handler),
		locker:    &sync.Mutex{},
		doneChs:   make(map[string]chan struct{}),
		closeOnce: &sync.Once{},
		closeCh:   make(chan struct{}),
	}
)

func init() {
	defaultHandlerManager.handlers[jiri.HandlerName] = jiri.DefaultJiriClient
	defaultHandlerManager.handlers[githttpbackend.HandlerName] = githttpbackend.DefaultGitHttpBackendClient
}

// TSK:
func handle(handleFn func(*config.Project) error, closeFn func(*config.Project) error, project *config.Project, doneCh <-chan struct{}, closeCh <-chan struct{}) {
	var (
		retryTimes, maxTimes = 0, 30
		before, after        = project.GetPlugins()
	)

	for {
		for ; retryTimes < maxTimes; retryTimes++ {
			logger.Infof("Start %v", project)
			err := plugin.Handle(project, before)
			if err != nil {
				continue
			}
			err = handleFn(project)
			if err != nil {
				continue
			}
			err = plugin.Handle(project, after)
			if err != nil {
				continue
			}
			logger.Infof("Finish %v", project)
			break
		}
		if project.Interval == 0 {
			return
		}
		retryTimes = 0
		select {
		case <-doneCh:
			closeFn(project)
			return
		case <-closeCh:
			closeFn(project)
			return
		case <-time.After(time.Duration(int64(project.Interval) * time.Hour.Nanoseconds())):
		}
	}
}

func (hm *handlerManager) Handle(project *config.Project) {
	hm.locker.Lock()
	defer hm.locker.Unlock()

	doneCh := make(chan struct{})
	hm.doneChs[project.Name] = doneCh

	go handle(hm.handlers[string(project.Type)].Handle, hm.handlers[string(project.Type)].Close, project, doneCh, hm.closeCh)
}

func (hm *handlerManager) Wait() {
	<-hm.closeCh
}

func (hm *handlerManager) Close(project *config.Project) {
	hm.locker.Lock()
	defer hm.locker.Unlock()

	close(hm.doneChs[project.Name])
	delete(hm.doneChs, project.Name)
}

func (hm *handlerManager) Shutdown() {
	hm.closeOnce.Do(func() { close(hm.closeCh) })
}

func (hm *handlerManager) Templet() {
	for _, handler := range hm.handlers {
		logger.Infof("handler templet:\n%s", handler.Templet())
	}
}

func (hm *handlerManager) Test(projects []config.Project) error {
	for _, project := range projects {
		if handler, exist := hm.handlers[project.Type]; !exist {
			logger.Errorf("project: %s, type: %s\n  error: not suppport\n", project.Name, project.Type)
			return fmt.Errorf("not suppport %s type project", project.Type)
		} else {
			err := handler.Test(&project)
			if err != nil {
				logger.Infof("project: %s, type: %s\ntemplet:\n%s\n", project.Name, project.Type, handler.Templet())
				return err
			}
		}
		err := plugin.Test(&project, project.Plugins)
		if err != nil {
			return err
		}
	}
	return nil
}
